package com.caregiver.watch.business.user.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.caregiver.watch.business.user.model.dto.AccessToken;
import com.caregiver.watch.business.user.model.dto.LoginUser;
import com.caregiver.watch.business.user.model.po.User;
import com.caregiver.watch.business.user.utils.SecurityUtils;
import com.caregiver.watch.business.user.utils.id.IdUtils;
import com.caregiver.watch.common.constant.CacheConstants;
import com.caregiver.watch.common.constant.SecurityConstants;
import com.caregiver.watch.common.redis.service.RedisService;
import com.caregiver.watch.common.utils.jwt.JwtUtils;
import com.caregiver.watch.common.utils.servlet.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.caregiver.watch.business.user.utils.SecurityUtils.getTokenKey;

/**
 * token验证处理
 *
 * @author yinsitech
 */
@Component
@Slf4j
public class TokenService {

    @Autowired
    private RedisService redisService;

    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private final static long EXPIRE_TIME = CacheConstants.EXPIRATION;

    private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;

    private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;

    /**
     * 创建令牌
     * @param user
     * @return
     */
    public AccessToken createToken(User user) {
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(user.getUserId());
        loginUser.setUsername(user.getUsername());
        return createToken(loginUser);
    }
    /**
     * 创建令牌
     */
    public AccessToken createToken(LoginUser user) {
        String token = IdUtils.fastUUID();
        user.setToken(token);
        user.setLoginTime(System.currentTimeMillis());
        user.setExpireTime(user.getLoginTime() + EXPIRE_TIME * MILLIS_MINUTE);
        Long userId = user.getUserId();
        String userName = user.getUsername();
        // Jwt存储信息
        Map<String, Object> claimsMap = new HashMap<>();
        claimsMap.put(SecurityConstants.USER_KEY, token);
        claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
        claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
        String accessToken = JwtUtils.createToken(claimsMap);
        // 清理旧token缓存
        cleanOldToken(userId);
        // 设置新的缓存
        String userKey = getTokenKey(token);
        redisService.setCacheObject(CacheConstants.LOGIN_USER_TOKEN + user.getUserId(), token, EXPIRE_TIME, TimeUnit.MINUTES);
        redisService.setCacheObject(userKey, user, EXPIRE_TIME, TimeUnit.MINUTES);
        return AccessToken.create(accessToken, EXPIRE_TIME);
    }

    /**
     * 清理旧token缓存
     */
    private void cleanOldToken(Long userId) {
        String oldToken = redisService.getCacheString(CacheConstants.LOGIN_USER_TOKEN + userId);
        if (StringUtils.isNotBlank(oldToken)) {
            redisService.deleteObject(CacheConstants.LOGIN_USER_TOKEN + userId);
            redisService.deleteObject(getTokenKey(oldToken));
        }
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser() {
        return getLoginUser(ServletUtils.getRequest());
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        String token = SecurityUtils.getToken(request);
        return getLoginUser(token);
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        try {
            String userKey = JwtUtils.getUserKey(token);
            return getLoginUserByUserKey(userKey);
        } catch (Exception e) {
            log.error("获取用户信息异常", e);
        }
        return null;
    }

    public LoginUser getLoginUserByUserKey(String userKey) {
        if (StringUtils.isEmpty(userKey)) {
            return null;
        }
        try {
            return redisService.getCacheObject(getTokenKey(userKey), new TypeReference<LoginUser>() {});
        } catch (Exception e) {
            log.error("获取用户信息异常 userKey:{}", userKey, e);
        }
        return null;
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUser loginUser) {
        if (Objects.isNull(loginUser) || Objects.isNull(loginUser.getToken())) {
            return;
        }
        refreshToken(loginUser);
    }

    /**
     * 删除用户缓存信息
     */
    public LoginUser delLoginUser(String token) {
        if (StringUtils.isBlank(token)) {
            return null;
        }
        try {
            String userKey = JwtUtils.getUserKey(token);
            LoginUser loginUser = redisService.getCacheObject(getTokenKey(userKey), new TypeReference<LoginUser>() {});
            if (loginUser != null) {
                redisService.deleteObject(CacheConstants.LOGIN_USER_TOKEN + loginUser.getUserId());
            }
            redisService.deleteObject(getTokenKey(userKey));
            return loginUser;
        } catch (Exception e) {
            log.error("删除用户缓存信息异常", e);
            return null;
        }
    }

    /**
     * 验证令牌有效期，相差不足120分钟，自动刷新缓存
     *
     * @param loginUser
     */
    public void verifyToken(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + EXPIRE_TIME * MILLIS_MINUTE);
        String userKey = getTokenKey(loginUser.getToken());
        redisService.setCacheObject(CacheConstants.LOGIN_USER_TOKEN + loginUser.getUserId(), loginUser.getToken(), EXPIRE_TIME, TimeUnit.MINUTES);
        redisService.setCacheObject(userKey, loginUser, EXPIRE_TIME, TimeUnit.MINUTES);
    }
}
